/**
 * 
 */
package pq.net;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Formatter;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import pq.util.AppConfigReader;

/**
 * @author tqthe
 * 
 */
public class CaroClient extends JFrame implements Runnable {

	private JTextField idField; // textfield to display player's mark
	private JTextArea displayArea; // JTextArea to display output
	private JPanel boardPanel; // panel for tic-tac-toe board
	private JPanel panel2; // panel to hold board
	private Cell board[][]; // caro board
	private Cell currentCell; // current cell
	private Socket connection; // connection to server
	private Scanner input; // input from server
	private Formatter output; // output to server
	private String caroHost; // host name for server
	private String myMark; // this client's mark
	private boolean myTurn; // determines which client's turn it is
	private final String X_MARK = "X"; // mark for first client
	private final String O_MARK = "O"; // mark for second client

	private int port;
	private int boardRows;
	private int boardColumns;

	/**
	 * Set up GUI
	 */
	public CaroClient() {

		setLookFeel();
		loadClientSettings("client-config.xml");

		displayArea = new JTextArea(4, 30);
		add(new JScrollPane(displayArea), BorderLayout.SOUTH);

		boardPanel = new JPanel();
		boardPanel.setLayout(new GridLayout(boardRows, boardColumns));
		board = new Cell[boardRows][boardColumns];

		for (int i = 0; i < board.length; i++) {
			for (int j = 0; j < board[i].length; j++) {
				board[i][j] = new Cell("", new Location(i, j));
				boardPanel.add(board[i][j]);
			}
		}

		idField = new JTextField(); // set up textfield
		idField.setEditable(false);
		add(idField, BorderLayout.NORTH);

		panel2 = new JPanel();
		panel2.add(boardPanel, BorderLayout.CENTER);
		add(panel2, BorderLayout.CENTER);

		setSize(500, 500);
		setVisible(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	/**
	 * Start the client thread
	 */
	public void startClient() {

		// connect to server, get streams and start outputThread
		try {
			// make connection to server
			connection = new Socket(InetAddress.getByName(caroHost), 12345);

			// get stream for input and output
			input = new Scanner(connection.getInputStream());
			output = new Formatter(connection.getOutputStream());
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		// create and start worker thread for this client
		ExecutorService worker = Executors.newFixedThreadPool(1);
		worker.execute(this);
	}

	/* 
	 * 
	 */
	@Override
	public void run() {

		myMark = input.nextLine(); // get player's mark (X or O)

		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {

				// display player's mark
				idField.setText("You are player \"" + myMark + "\"");
			}
		});

		myTurn = (myMark.equals(X_MARK)); // determine if client's turn

		// receive messages sent to client and output them
		while (true) {
			if (input.hasNextLine()) {
				processMessage(input.nextLine());
			}
		}
	}

	/**
	 * Process messages received by client
	 * 
	 * @param message
	 *            - The message to process
	 */
	private void processMessage(String message) {

		// valid move occurred
		if (message.equals("Valid move.")) {
			displayMessage("Valid move, please wait.\n");
			setMark(currentCell, myMark); // set mark in square
		} else if (message.equals("Invalid move, try again")) {
			displayMessage(message + "\n"); // display invalid move
			myTurn = true; // still this client's turn
		} else if (message.equals("Opponent moved")) {
			int row = input.nextInt();
			int column = input.nextInt();
			input.nextLine(); // skip newline after int location
			setMark(board[row][column], (myMark.equals(X_MARK) ? O_MARK
					: X_MARK)); // mark move
			displayMessage("Opponent moved. Your turn.\n");
			myTurn = true; // now this client's turn
		} else {
			displayMessage(message + "\n"); // display the message
		}
		
	}

	/**
	 * Manipulate outputArea in event-dispatch thread
	 * 
	 * @param message
	 *            - The message to display
	 */
	private void displayMessage(final String message) {

		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {

				displayArea.append(message);
			}
		});
	}

	/**
	 * Utility method to set mark on board in event-dispatch thread
	 * 
	 * @param cell
	 *            - The cell to mark
	 * @param mark
	 *            - The mark to mark to the cell
	 */
	private void setMark(final Cell cell, final String mark) {

		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {

				cell.setMark(mark);
			}
		});
	}

	/**
	 * Send message to server indicating clicked cell
	 * 
	 * @param cellLocation
	 *            - The location of the cell to be sent
	 */
	public void sendClickedCell(Location cellLocation) {

		// if it is my turn
		if (myTurn) {
			output.format("%s %s\n", cellLocation.getRow(), cellLocation
					.getColumn());
			output.flush();

			myTurn = false; // not my turn anymore
		}
	}

	/**
	 * Set current cell
	 * 
	 * @param cell
	 *            - The cell being become current cell
	 */
	public void setCurrentCell(Cell cell) {

		currentCell = cell;
	}

	/*
	 * The cell of caro board game
	 */
	private class Cell extends JPanel {

		private String mark; // mark to be drawn in this square
		private Location location; // location of square

		/**
		 * Construct the cell with with cell mark and cell location.
		 * 
		 * @param cellMark
		 *            - The cell mark (X or O)
		 * @param location
		 *            - The location of cell
		 */
		public Cell(String cellMark, Location cellLocation) {

			mark = cellMark;
			location = cellLocation;

			// handle mouse click
			addMouseListener(new MouseAdapter() {

				@Override
				public void mouseReleased(MouseEvent e) {

					super.mouseReleased(e);
					setCurrentCell(Cell.this);

					// send location of this cell
					sendClickedCell(getCellLocation());
				}
			});
		}

		/**
		 * Return preferred size of Square
		 * 
		 * @return The preferred size of Square
		 */
		public Dimension getPreferredSize() {

			return new Dimension(30, 30);
		}

		/**
		 * Return minimum size of Square
		 */
		public Dimension getMinimumSize() {

			return getPreferredSize();
		}

		/**
		 * Set new mark for the cell
		 * 
		 * @param newMark
		 *            - The mark to set
		 */
		public void setMark(String newMark) {

			mark = newMark;
			repaint();
		}

		/**
		 * Get the location of the cell
		 * 
		 * @return
		 */
		public Location getCellLocation() {

			return location;
		}

		/**
		 * Draw cell
		 */
		public void paintComponent(Graphics g) {

			super.paintComponent(g);

			g.drawRect(0, 0, 29, 29);

			Color color = mark.equals(X_MARK) ? Color.RED : Color.BLUE;
			g.setColor(color);
			g.setFont(new Font("Serif", Font.BOLD, 12));
			g.drawString(mark, 11, 20);
		}

	} // end class Cell

	/**
	 * This class represent the location of the cell on the caro board
	 * 
	 * @author tqthe
	 * 
	 */
	private class Location {

		/**
		 * The row index (zero-base)
		 */
		private int row;

		/**
		 * The column index (zero-base)
		 */
		private int column;

		/**
		 * Default constructor
		 */
		public Location() {

			// re-use code ;)
			this(0, 0);
		}

		/**
		 * Construct Location object with row and column index
		 * 
		 * @param row
		 *            - Row index
		 * @param column
		 *            - Column index
		 */
		public Location(int row, int column) {

			setRow(row);
			setColumn(column);
		}

		/**
		 * Set the row
		 * 
		 * @param row
		 *            - The row value to set
		 */
		public void setRow(int row) {

			this.row = row;
		}

		/**
		 * Get the row
		 * 
		 * @return The index of row (zero-base)
		 */
		public int getRow() {

			return row;
		}

		/**
		 * Set the column
		 * 
		 * @param column
		 *            - The column value to set
		 */
		public void setColumn(int column) {

			this.column = column;
		}

		/**
		 * Get the column
		 * 
		 * @return The index of column (zero-base)
		 */
		public int getColumn() {

			return column;
		}

	} // end class Location

	/**
	 * Load client configuration from XML file
	 * 
	 * @param configFile
	 *            - The xml file that contains settings
	 */
	private void loadClientSettings(String configFile) {

		// Load application configuration
		AppConfigReader configer = new AppConfigReader(configFile);

		caroHost = configer.getValue("ip");
		port = Integer.parseInt(configer.getValue("port"));
		boardRows = Integer.parseInt(configer.getValue("boardRows"));
		boardColumns = Integer.parseInt(configer.getValue("boardColumns"));
	}

	/**
	 * Make GUI look like Mac OS X GUI
	 */
	private void setLookFeel() {

		// set system properties here that affect Quaqua
		// for example the default layout policy for tabbed
		// panes:
		System.setProperty("Quaqua.tabLayoutPolicy", "wrap");

		try {
			UIManager
					.setLookAndFeel("ch.randelshofer.quaqua.QuaquaLookAndFeel");
		} catch (ClassNotFoundException e1) {
			e1.printStackTrace();
		} catch (InstantiationException e1) {
			e1.printStackTrace();
		} catch (IllegalAccessException e1) {
			e1.printStackTrace();
		} catch (UnsupportedLookAndFeelException e1) {
			e1.printStackTrace();
		}
	}

} // end class CaroClient
